

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=&#34;auto&#34;>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/favicon.png">
  <link rel="icon" type="image/png" href="/img/favicon.png">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="description" content="">
  <meta name="author" content="Lin">
  <meta name="keywords" content="">
  <title>Java 高频知识点之 JVM - Vin</title>

  <link  rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.5.3/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/4.0.0/github-markdown.min.css" />
  <link  rel="stylesheet" href="/lib/hint/hint.min.css" />

  
    
    
      
      <link  rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/10.1.2/styles/androidstudio.min.css" />
    
  

  
    <link  rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css" />
  



<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_ba1fz6golrf.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_kmeydafke9r.css">


<link  rel="stylesheet" href="/css/main.css" />

<!-- 自定义样式保持在最底部 -->

  
<link rel="stylesheet" href="/css/custom.css">



  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    var CONFIG = {"hostname":"example.com","root":"/","version":"1.8.5","typing":{"enable":true,"typeSpeed":50,"cursorChar":"_","loop":false},"toc":{"enable":true,"headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"anchorjs":{"enable":false,"element":"h1,h2,h3,h4,h5,h6","placement":"right","visible":"hover","icon":""},"copy_btn":true,"image_zoom":{"enable":true},"lazyload":{"enable":true,"onlypost":false},"web_analytics":{"enable":false,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null}}};
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
<meta name="generator" content="Hexo 5.2.0"></head>


<body>
  <header style="height: 60vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand"
       href="/">&nbsp;<strong>Vin's Blog</strong>&nbsp;</a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;<i
                class="iconfont icon-search"></i>&nbsp;</a>
          </li>
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" href="javascript:">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="banner intro-2" id="background" parallax=true
         style="background: url('https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201001115214.jpg') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="page-header text-center fade-in-up">
            <span class="h2" id="subtitle" title="Java 高频知识点之 JVM">
              
            </span>

            
              <div class="mt-3">
  
  
    <span class="post-meta">
      <i class="iconfont icon-date-fill" aria-hidden="true"></i>
      <time datetime="2020-11-11 08:38" pubdate>
        2020年11月11日 早上
      </time>
    </span>
  
</div>

<div class="mt-1">
  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      4.4k 字
    </span>
  

  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      46
       分钟
    </span>
  

  
  
</div>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid">
  <div class="row">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-md">
      <div class="container nopadding-md" id="board-ctn">
        <div class="py-5" id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">Java 高频知识点之 JVM</h1>
            
            <div class="markdown-body">
              <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>看过很多面经，经常混淆 JVM 内存结构和 Java 内存模型的概念，讲解之前先做好区分。</p>
<p>JVM 内存结构是<strong>与 JVM 的内部存储结构相关</strong>，而 Java 内存模型是与<strong>多线程编程相关</strong>。</p>
<h1 id="JVM-内存结构"><a href="#JVM-内存结构" class="headerlink" title="JVM 内存结构"></a>JVM 内存结构</h1><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>先来了解下 JVM 整体的构成</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111102903.png" srcset="/img/loading.gif" alt="image-20201111102833057"></p>
<p>具体每个区域执行的功能如下</p>
<ul>
<li>Java 源代码编译成Java Class文件后通过类加载器 ClassLoader 加载到 JVM 中</li>
</ul>
<ul>
<li><mark>类</mark>存放在<strong>方法区</strong>中</li>
<li>类创建的<mark>对象</mark>存放在<strong>堆</strong>中</li>
<li>堆中<mark>对象的调用方法</mark>时会使用到<strong>虚拟机栈，本地方法栈，程序计数器</strong></li>
<li>方法执行时每行代码由<strong>解释器</strong>逐行执行</li>
<li>热点代码由 <strong>JIT 编译器</strong>即时编译</li>
<li><strong>垃圾回收机制</strong>回收堆中资源</li>
<li>和操作系统打交道需要调用<strong>本地方法接口</strong></li>
</ul>
<h2 id="内存结构分析"><a href="#内存结构分析" class="headerlink" title="内存结构分析"></a>内存结构分析</h2><h3 id="程序计数器"><a href="#程序计数器" class="headerlink" title="程序计数器"></a>程序计数器</h3><p>程序计数器主要用于记录当前线程执行的位置(虚拟机字节码指令的地址)。</p>
<p>Java 程序时多线程的，CPU可以在多个线程中分配时间片，当一个线程被CPU挂起后，需要记录代码执行的位置，而程序计数器就是来存储该指令的地址。</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111104338.png" srcset="/img/loading.gif" alt="image-20201111104336475"></p>
<ul>
<li>程序计数器 <strong>不会发生内存溢出(OOP)</strong> ，因为其存储的内容仅仅就是下一个需要待执行的命令的地址。</li>
<li>程序计数器是线程私有的，每个线程单独持有一个计数器，它的生命周期随着线程的创建而创建，随着线程的结束而死亡。</li>
<li>当线程在执行 Java 方法时，计数器记录指令地址；而线程在执行 Native 方法时，<strong>计数器值为空</strong>。</li>
</ul>
<h3 id="虚拟机栈"><a href="#虚拟机栈" class="headerlink" title="虚拟机栈"></a>虚拟机栈</h3><p>每个线程在创建时都会创建一个虚拟机栈，当线程在执行方法时会创建一个<strong>栈帧</strong>。</p>
<p>栈帧存储<strong>局部变量表、操作栈、动态链接、方法出口</strong>等信息。</p>
<p>每个线程只有一个<strong>活动栈帧</strong>（栈顶的栈帧），对应着正在执行的代码。</p>
<p>调用方法时入栈，方法返回时出栈。</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111105839.png" srcset="/img/loading.gif" alt="image-20201111105838370"></p>
<p>在这个区域会出现两种异常：</p>
<ul>
<li>StackOverflowError (栈内存溢出)：当线程请求栈深度超出虚拟机栈所允许的深度时抛出。</li>
<li>OutOfMemoryError (堆内存溢出)：当 Java 虚拟机无法申请足够内存时抛出。</li>
</ul>
<h4 id="常见问题解析"><a href="#常见问题解析" class="headerlink" title="常见问题解析"></a>常见问题解析</h4><ul>
<li><p>垃圾回收是否涉及栈内存：不涉及，垃圾回收只涉及堆内存。</p>
</li>
<li><p>栈内存分配越大越好吗：内存一定时，<strong>栈内存越大，线程数越少</strong>，所以不宜过大。</p>
</li>
<li><p>方法内的局部变量是<strong>线程安全</strong>的吗：</p>
<p>需要分类讨论，总体而言对象类型的变量以及静态局部变量是线程不安全的</p>
<ul>
<li>线程安全：普通局部变量、参数传入基本数据类型变量、基本数据类型局部变量</li>
<li>线程不安全：静态的局部变量、对象类型的局部变量、参数传入对象类型变量</li>
</ul>
</li>
<li><p>造成栈内存溢出 (<strong>StackOverflowError</strong>)</p>
<ol>
<li>栈帧过多：如递归调用没有正确设置结束条件</li>
<li>栈帧过大：json数据转换时对象嵌套多个对象 ，如用户类有部门类属性，部门类由用户类属性</li>
<li>根据线程运行结果来判断<ol>
<li><strong>CPU占用过高</strong>（定位问题）<ul>
<li><code>top</code> 命令获取进程编号，查找占用高的进程</li>
<li><code>ps H -eo pid,tid,%cpu | grep &lt;进程号&gt;</code> 命令获取线程的进程 id，线程 id，cpu 占用</li>
<li>将查看到的占用高的线程编号转换为16进制的数：如 6626 -&gt; 19E2</li>
<li><code>jstack &lt;进程id&gt;</code> 获取进程栈信息</li>
<li>问题线程的最开始 <code>#数字</code> 表示出问题的行数，回到代码查看</li>
</ul>
</li>
<li><strong>程序运行很长时间没有结果</strong>（死锁问题）<ul>
<li><code>jstack &lt;进程id&gt;</code> 获取进程栈信息</li>
<li>查看最后 20 行左右有无 <code>Found one Java-level deadlock</code></li>
<li>查看死锁的详细信息描述和问题定位</li>
<li>回到代码中定位代码解决问题</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
</ul>
<h3 id="本地方法栈"><a href="#本地方法栈" class="headerlink" title="本地方法栈"></a>本地方法栈</h3><p>本地方法栈与虚拟机栈的功能类似，执行 Java 方法使用虚拟机栈，<strong>执行 Native 方法使用本地方法栈</strong>。</p>
<p>Native 方法是 Java 通过本地方法接口 (<strong>JNI</strong>) 直接<strong>调用本地 C/C++ 库</strong>，可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口。例如notify，hashcode，wait等都是native方法。</p>
<h3 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h3><p>堆是 JVM 中最大的一块内存，它有如下的作用和属性：</p>
<ul>
<li>堆是线程共享的，它的主要作用是用来<strong>存放 Java 对象实例</strong>，即 <strong>new 关键字创建的对象</strong>；</li>
<li>因此堆也是 <strong>GC 管理的主要区域</strong>，当堆中没有可用空间时，<strong>会抛出 OOM 异常</strong>，如死循环创建对象；</li>
</ul>
<p>Java 堆可以细分为<strong>新生代与老年代</strong>，而新生代可以分为 Eden 空间和 Survivor 空间，进一步划分的目的是<strong>更好地回收内存或更快的分配内存</strong>。</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111120054.png" srcset="/img/loading.gif" alt="堆结构"></p>
<h4 id="堆内存诊断"><a href="#堆内存诊断" class="headerlink" title="堆内存诊断"></a><strong>堆内存诊断</strong></h4><ul>
<li>命令行方式<ul>
<li><code>jps</code> 获取运行进程号</li>
<li><code>jmap -heap &lt;进程号&gt;</code> 查看当前时刻的堆内存信息</li>
</ul>
</li>
<li>jconsole<ul>
<li>命令行输入jconsole打开可视化的界面连接上进程</li>
<li>可视化的检测连续的堆内存信息</li>
</ul>
</li>
<li>jvisualvm<ul>
<li>命令行输入jvisualvm打开可视化界面选择进程</li>
<li>可视化的查看堆内存信息</li>
</ul>
</li>
</ul>
<h3 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h3><p>方法区主要存储被已经被 JVM 加载的<strong>类信息（版本、字段、方法等）、常量、静态变量和编译后的代码</strong>等数据，是被线程共享的区域。</p>
<p>方法区和永久代的关系：</p>
<ul>
<li>方法区和永久代的关系很像 Java 中<strong>接口和类</strong>的关系，类实现了接口，而永久代就是HotSpot虚拟机对虚拟机规范中方法区的⼀种实现方式。</li>
</ul>
<ul>
<li>方法区是 JVM 规范中规定的一块区域，规定了这一区域要存放哪些数据。</li>
<li>HotSpot1.6 使用永久代作为方法区的实现</li>
<li>HotSpot1.8 使用本地内存的元空间作为方法区的实现（但<strong>字符串常量池</strong>还是放在堆中）</li>
</ul>
<p>版本迭代如下图</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111124424.png" srcset="/img/loading.gif" alt="image-20201111123329877"></p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111124433.png" srcset="/img/loading.gif" alt="image-20201111124407429"></p>
<h4 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h4><ol>
<li><p><strong>字符串常量池特性</strong></p>
<ul>
<li>字符串常量池中声明的<strong>字符串仅为字符</strong>，<strong>首次使用时才变为堆上的对象</strong></li>
<li>利用字符串池机制，可以避免重复创建字符串</li>
<li>字符串常量拼接原理是编译器优化 StringBuilder (1.8)</li>
<li>字符串常量池在 jdk 1.6 放在永久代，在 jdk 1.8 放在堆空间</li>
<li><strong>intern 方法</strong>：先判断串池中是否有该字符串，如果有则<strong>返回串池的字符串</strong>；否则将此String对象添加到池中，并<strong>返回堆上该String对象的引用</strong>。 <ul>
<li>1.8 和 1.6 的区别在于<strong>放入池的方式</strong>不同，1.6 是复制到串池里，1.8是将堆上的字符串移动到串池里。</li>
</ul>
</li>
</ul>
<p>字符串常量池分析 (jdk 1.8)</p>
<div class="hljs"><pre class="hljs-wrap"><code data-rel="Code" class="hljs java">String s1 = <span class="hljs-string">&quot;a&quot;</span>;
String s2 = <span class="hljs-string">&quot;b&quot;</span>;
String s3 = <span class="hljs-string">&quot;a&quot;</span>+<span class="hljs-string">&quot;b&quot;</span>;
String s4 = s1+s2;
String s5 = <span class="hljs-string">&quot;ab&quot;</span>;
String s6 = s4.intern();


System.out.println(s3==s4);<span class="hljs-comment">// s3在常量池中，s4在堆上（intern尝试s4放入常量池，因为ab存在了就拒绝放入返回ab引用给s6，s4还是堆上的）</span>
System.out.println(s3==s5);<span class="hljs-comment">// s3在常量池中，s5也在常量池中（字符串编译期优化）</span>
System.out.println(s3==s6);<span class="hljs-comment">// s3在常量池中，s6是s4的intern返回常量池中ab的引用，所以也在常量池中</span>

<span class="hljs-comment">/* false, true, true */</span>

String x2 = <span class="hljs-keyword">new</span> String(<span class="hljs-string">&quot;c&quot;</span>)+<span class="hljs-keyword">new</span> String(<span class="hljs-string">&quot;d&quot;</span>);
String x1 = <span class="hljs-string">&quot;cd&quot;</span>;
x2.intern();

System.out.println(x1==x2);<span class="hljs-comment">//x2调用intern尝试放入常量池，但常量池中已经有cd了，所以只是返回一个cd字符串引用，而x2还是堆上的引用</span>
<span class="hljs-comment">/* false */</span></code></pre></div>


</li>
</ol>
<ol start="2">
<li><p>JVM 调优三大参数</p>
<ul>
<li>-Xss：规定了每个线程虚拟机栈的大小，影响并发线程数量；</li>
<li>-Xms：堆大小的初始值，超过初始值会扩容到最大值；</li>
<li>-Xmx：对大小的最大值，一般保持两者一致，扩容会导致内存抖动，影响程序运行稳定性；</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>JVM 内存结构中堆和栈的区别</p>
<ul>
<li>管理方式：栈自动释放，堆需要 GC</li>
<li>空间大小：栈比堆小</li>
<li>碎片：栈产生的碎片远少于堆</li>
<li>分配方式：栈支持静态分配和动态分配，堆支持动态分配</li>
<li>效率：栈的效率比堆高</li>
</ul>
</li>
</ol>
<h2 id="Java-对象的创建过程"><a href="#Java-对象的创建过程" class="headerlink" title="Java 对象的创建过程"></a>Java 对象的创建过程</h2><p>下图展示的是 Java 对象创建的过程</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111141032.png" srcset="/img/loading.gif" alt="image-20201111141019059"></p>
<h3 id="1-类加载检查"><a href="#1-类加载检查" class="headerlink" title="1. 类加载检查"></a>1. 类加载检查</h3><p>虚拟机遇到 <code>new</code> 指令时，先检查该指令是否能在常量池中定位到该类的符号引用，并且检查该符号引用代表的类是否已被加载、解析和初始化过，若没有则先执行类加载过程。</p>
<h3 id="2-分配内存"><a href="#2-分配内存" class="headerlink" title="2. 分配内存"></a>2. 分配内存</h3><p>当类加载检查通过后，会在 Java 堆里为该类对象分配内存区域，分配方式有<strong>指针碰撞</strong>和<strong>空闲列表</strong>两种，具体情况是根据 <strong>Java 堆的规整情况</strong>所决定。</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111144010.png" srcset="/img/loading.gif" alt="image-20201111144002956"></p>
<p>在创建对象时，需要注意到线程安全的问题，在高并发的情况下，创建对象是频繁的事情，虚拟机提供两种方式来解决<strong>内存分配的并发问题</strong>：</p>
<ul>
<li><strong>CAS+失败重试：</strong>CAS 是乐观锁的一种实现方式，即在不加锁的情况下完成某项操作，若遇到冲突失败则重试，直至成功为止。虚拟机采用 CAS+失败重试来保证<strong>更新操作的原子性</strong>。</li>
<li><strong>TLAB：</strong>为每一个线程预先在 Eden 区分配一小块内存，当需要给线程的对象分配内存时，先在 TLAB 分配；当 TLAB 内存不足时再使用上述的 <strong>CAS+失败重试</strong>操作进行内存分配。</li>
</ul>
<h3 id="3-初始化零值"><a href="#3-初始化零值" class="headerlink" title="3. 初始化零值"></a>3. 初始化零值</h3><p>当内存分配完成后，虚拟机需要将已分配的内存空间初始化为对应零值（<strong>不包括对象头</strong>），保证对象的实例字段在不赋初值时就可使用。</p>
<h3 id="4-设置对象头"><a href="#4-设置对象头" class="headerlink" title="4. 设置对象头"></a>4. 设置对象头</h3><p>初始化零值完成后，虚拟机要对对象添加对象头，<strong>对象头包括类的元数据信息，对象的哈希码，对象的GC分代年龄</strong>等信息。</p>
<h3 id="5-执行-init-方法"><a href="#5-执行-init-方法" class="headerlink" title="5. 执行 init 方法"></a>5. 执行 init 方法</h3><p>在上述工作都完成后，虚拟机的对象已准备就绪了，此时执行 <code>new</code> 指令之后的 <code>init</code> 方法，将<strong>对象按照程序员的意愿进行初始化</strong>，一个真正可用的对象才完全产生出来。</p>
<h2 id="GC-垃圾回收机制"><a href="#GC-垃圾回收机制" class="headerlink" title="GC 垃圾回收机制"></a>GC 垃圾回收机制</h2><p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201112154632.png" srcset="/img/loading.gif" alt="image-20201112154621114"></p>
<h3 id="1-垃圾判别方法"><a href="#1-垃圾判别方法" class="headerlink" title="1. 垃圾判别方法"></a>1. 垃圾判别方法</h3><p>虚拟机引入两种判别方式：<strong>引用计数算法</strong>和<strong>可达性分析算法</strong></p>
<h4 id="引用计数算法"><a href="#引用计数算法" class="headerlink" title="引用计数算法"></a>引用计数算法</h4><p>每个对象实例都有一个引用计数器，<strong>被引用则+1，完成引用则-1</strong>。判断对象的<strong>引用数量</strong>来决定对象是否可以被回收。</p>
<ul>
<li>优点：执行效率高，程序执行受影响小</li>
<li>缺点：<strong>无法检测出循环引用</strong>的情况，导致内存泄露</li>
</ul>
<h4 id="可达性分析算法"><a href="#可达性分析算法" class="headerlink" title="可达性分析算法"></a>可达性分析算法</h4><p>JVM 把内存中所有对象之间的引用关系看做一张图，以 GC Root 对象为起始点，从这些节点开始向下搜索，最后判断对象的引用与 GC Root 是否具有可达路径来决定对象是否可以被回收。</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111150218.png" srcset="/img/loading.gif" alt="image-20201111150213926"></p>
<p><strong>常见的 GC Root 有以下几种：</strong></p>
<ol>
<li>Java 虚拟机栈中引用的对象</li>
<li>方法区的常量引用对象和类静态引用的对象</li>
<li>处于激活状态的线程对象</li>
<li>本地方法栈中 JNI 的引用对象</li>
<li>GC 保留的对象，如系统类加载器</li>
</ol>
<p><strong>何时触发回收机制</strong></p>
<ol>
<li>Allocation Failure：剩余堆内存不足导致内存分配失败时</li>
<li>手动调用 <code>System.gc()</code></li>
</ol>
<h3 id="2-垃圾回收算法"><a href="#2-垃圾回收算法" class="headerlink" title="2. 垃圾回收算法"></a>2. 垃圾回收算法</h3><h4 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h4><p><strong>标记没有被 GC Root 引用的对象，清除被标记位置的内存。</strong></p>
<p>优点：处理速度快；缺点：造成空间不连续，<strong>产生内存碎片</strong>。</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111153056.png" srcset="/img/loading.gif" alt="image-20201111153054782"></p>
<h4 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a>标记-整理算法</h4><p><strong>标记没有被 GC Root 引用的对象，整理被引用的对象。</strong></p>
<p>优点：空间连续，没有内存碎片；缺点：整理效率较低</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111153757.png" srcset="/img/loading.gif" alt="image-20201111153755615"></p>
<h4 id="复制算法"><a href="#复制算法" class="headerlink" title="复制算法"></a>复制算法</h4><p>步骤：</p>
<ol>
<li>分配同等大小的内存空间</li>
<li>标记被GC Root引用的对象</li>
<li>将引用的对象连续的复制到新的内存空间</li>
<li>清除原来的内存空间</li>
<li>交换FROM空间和TO空间</li>
</ol>
<p>优点：空间连续，没有内存碎片；缺点：占用双倍的内存空间</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111155643.png" srcset="/img/loading.gif" alt="image-20201111155629574"></p>
<h3 id="3-分代垃圾回收机制"><a href="#3-分代垃圾回收机制" class="headerlink" title="3. 分代垃圾回收机制"></a>3. 分代垃圾回收机制</h3><p>JVM 根据对象的存活周期不同，把内存划分为<strong>新生代</strong>和<strong>老年代</strong>。</p>
<p><strong>分代回收的策略</strong>就是：对于新创建的对象会在新生代中分配内存，此区域的对象生命周期一般较短，如果经过多次回收仍然存活下来，则将它们转移到老年代。</p>
<p>新生成的对象会优先存放在新生代中，新生代又可以细分为 3 个部分，Eden、Survivor1、Survivor2 三个部分，它们的内存大小比例为 8:1:1。对于 S0 和 S1，分配对象的区域被称为 from 去，复制到的区域被称为 to 区。在 S0 区和 S1 区切换几次后（默认 15 次），如果还有存活对象，说明这些对象的生命周期较长，将它们转移到老年代中。</p>
<p>老年代的内存一般比新生代要大，内存默认是新生代的 2 倍，能够存放更多的对象，如果对象比较大（比如长字符串或者大数组），这个大对象在创建时会直接分配到老年代上。</p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201111164720.png" srcset="/img/loading.gif" alt="默认堆分配"></p>
<p><strong>新生代</strong>中因为要进行一些复制操作，所以一般<strong>采用复制算法</strong>。</p>
<p><strong>老年代</strong>因为对象的生命周期较长，不需要过多的复制操作，一般<strong>采用标记压缩算法</strong>。</p>
<h3 id="4-垃圾收集器"><a href="#4-垃圾收集器" class="headerlink" title="4. 垃圾收集器"></a>4. 垃圾收集器</h3><blockquote>
<p>介绍垃圾收集器之前，先了解安全点 (SafePoint)，即分析过程中对象引用关系不会发生变化的点。</p>
<p>产生安全点的地方有：方法调用、循环跳转、异常跳转，另外，安全点的数量应该适中设置。</p>
</blockquote>
<h4 id="Serial-收集器-串行"><a href="#Serial-收集器-串行" class="headerlink" title="Serial 收集器 (串行)"></a>Serial 收集器 (串行)</h4><ul>
<li><strong>单线程</strong>的垃圾收集器，简单而高效</li>
<li><strong>STW</strong>：在进行垃圾收集工作的时候必须暂停其他所有的工作线程</li>
<li>堆内存较少，CPU核数少，适合单人电脑</li>
<li><strong>新生代采用<mark>复制算法</mark>，老年代采用<mark>标记-整理算法</mark>。</strong></li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201112091828.png" srcset="/img/loading.gif" alt="Serial GC"></p>
<h4 id="ParNew-收集器-多线程"><a href="#ParNew-收集器-多线程" class="headerlink" title="ParNew 收集器 (多线程)"></a>ParNew 收集器 (多线程)</h4><p>Serial 收集器的多线程版本，除了垃圾收集采用多线程方式外，其余与 Serial 收集器一样，常与 CMS 收集器配合工作。</p>
<p><strong>新生代采用<mark>复制算法</mark>，老年代采用<mark>标记-整理算法</mark>。</strong></p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201112091910.png" srcset="/img/loading.gif" alt="ParNew GC"></p>
<h4 id="Parallel-Scavenge-收集器-吞吐量优先"><a href="#Parallel-Scavenge-收集器-吞吐量优先" class="headerlink" title="Parallel Scavenge 收集器 (吞吐量优先)"></a>Parallel Scavenge 收集器 (吞吐量优先)</h4><p>Parallel Scavenge 收集器类似于 ParNew 收集器，区别在于其关注点是吞吐量(<strong>高效地利用CPU</strong>)，而 <strong>CMS</strong> 等垃圾收集器关注的更多是<strong>用户线程的停顿时间</strong>(用户体验)，因此可通过调整参数找到合适的停顿时间或最大吞吐量。</p>
<p><strong>新生代采用<mark>复制算法</mark>，老年代采用<mark>标记-整理算法</mark>。</strong></p>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201112091910.png" srcset="/img/loading.gif" alt="Parallel Scavenge"></p>
<h4 id="CMS-收集器-响应时间优先"><a href="#CMS-收集器-响应时间优先" class="headerlink" title="CMS 收集器(响应时间优先)"></a>CMS 收集器(响应时间优先)</h4><p>CMS (Concurrent Mark Sweep) 收集器是一种以<strong>获取最短回收停顿时间</strong>为目标的收集器，同时也是第一款<strong>并发收集器</strong>(垃圾回收线程和用户线程同时执行)。</p>
<p>优点：<strong>并发收集</strong>和<strong>低停顿</strong>；缺点：对CPU资源敏感，无法处理浮动垃圾，可能<strong>导致大量的内存碎片</strong></p>
<p>CMS 收集器是 <strong>“标记-清除”算法</strong> 实现的，执行步骤分为五步：</p>
<ul>
<li><strong>初始标记：</strong>STW，即暂停所有的其他线程 (快)</li>
<li><strong>并发标记：</strong><mark>并发</mark>追溯标记，程序不卡顿 (慢)</li>
<li><strong>重新标记：</strong>暂停虚拟机其他线程，扫描 CMS 堆中剩余对象。(快)</li>
<li><strong>并发清理：</strong><mark>并发</mark>清理垃圾对象，程序不会卡顿。</li>
<li><strong>并发重置：</strong>重置 CMS 收集器的数据结构。</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/img/2020/20201112095704.png" srcset="/img/loading.gif" alt="CMS"></p>
<h4 id="G1-收集器"><a href="#G1-收集器" class="headerlink" title="G1 收集器"></a>G1 收集器</h4><p>G1 (Garbage-First) 是 JDK 9 默认的垃圾收集器，主要针对多处理器和大容量内存的<strong>服务器</strong>，注重<strong>吞吐量和低延迟</strong>。</p>
<p>G1 收集器在后台维护了一个优先列表，根据<strong>收集时间和回收价值</strong>来划分内存空间来回收，提高回收效率。</p>
<p>整体上是<strong>标记-整理</strong>算法，两个区域之间是<strong>复制算法</strong>。</p>
<p>特点如下：</p>
<ul>
<li>并发与并行</li>
<li>分代收集</li>
<li>空间整合</li>
<li>可预测的停顿</li>
</ul>
<p>运行步骤：</p>
<ul>
<li>初始标记</li>
<li>并发标记</li>
<li>最终标记</li>
<li>筛选回收</li>
</ul>
<h4 id="JVM-主要相关参数"><a href="#JVM-主要相关参数" class="headerlink" title="JVM 主要相关参数"></a>JVM 主要相关参数</h4><ul>
<li>-XX: +UseParallelGC：使用 ParallelGC</li>
<li>-XX: +UseParallelOldGC：使用 ParallelGC 老年代版本</li>
<li>-XX: MaxGCPauseMillis：最大垃圾收集停顿时间</li>
<li>-XX: GCTimeRatio：设置吞吐量大小</li>
<li>-XX: UseAdaptiveSizePolicy：设置动态调整堆内存分配</li>
</ul>
<h1 id="Java-内存模型"><a href="#Java-内存模型" class="headerlink" title="Java 内存模型"></a>Java 内存模型</h1><p>未完待续</p>
<h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_41170102/article/details/104650162">https://blog.csdn.net/qq_41170102/article/details/104650162</a></li>
<li><a target="_blank" rel="noopener" href="https://add7.cc/java/jvm-nei-cun-mo-xing">https://add7.cc/java/jvm-nei-cun-mo-xing</a></li>
</ul>

            </div>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                  <div class="post-meta mr-3">
                    <i class="iconfont icon-category"></i>
                    
                      <a class="hover-with-bg" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a>
                    
                  </div>
                
                
                  <div class="post-meta">
                    <i class="iconfont icon-tags"></i>
                    
                      <a class="hover-with-bg" href="/tags/%E9%9D%A2%E8%AF%95/">面试</a>
                    
                      <a class="hover-with-bg" href="/tags/Java/">Java</a>
                    
                      <a class="hover-with-bg" href="/tags/Jvm/">Jvm</a>
                    
                  </div>
                
              </div>
              
              
                <div class="post-prevnext row">
                  <article class="post-prev col-6">
                    
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2020/10/24/10-24-Redis-Usage-State/">
                        <span class="hidden-mobile">Redis 的项目地位及使用场景</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    

    
      <a id="scroll-top-button" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
    

    
      <div class="col-lg-7 mx-auto nopadding-md">
        <div class="container custom mx-auto">
          <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vin9712/cdnRes@cdn/blog/hexo/css/font.css">
        </div>
      </div>
    
  </main>

  <footer class="text-center mt-5 py-3">
  <div class="footer-content">
     <span>©2020 by Lin<br>驱动</span> <a href="https://hexo.io" target="_blank" rel="nofollow noopener">Hexo</a> <span>|&nbsp;主题</span> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener">Fluid</a> <div> <span id="timeDate">载入天数...</span> <span id="times">载入时分秒...</span> <script src="/js/mod/duration.js"></script> </div> <div> <p style="font-size: 18px; font-weight: bold;" id="hitokoto">:D 获取中...</p> <script src="/js/mod/hitokoto.js"></script> </div> 
  </div>
  

  

  
</footer>

<!-- SCRIPTS -->

  <script  src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":200})
    NProgress.start()
    document.addEventListener('DOMContentLoaded', function() {
      window.NProgress && window.NProgress.inc();
    })
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://cdn.staticfile.org/jquery/3.5.1/jquery.min.js" ></script>
<script  src="https://cdn.staticfile.org/twitter-bootstrap/4.5.3/js/bootstrap.min.js" ></script>
<script  src="/js/debouncer.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>

<!-- Plugins -->


  
    <script  src="/js/lazyload.js" ></script>
  



  



  <script  src="https://cdn.staticfile.org/tocbot/4.12.0/tocbot.min.js" ></script>



  <script  src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>





  <script defer src="https://cdn.staticfile.org/clipboard.js/2.0.6/clipboard.min.js" ></script>




<!-- 1.8.5 的原版的不好用 还是换回来了 -->
<!-- 打字机特效 -->

  <script  src="https://cdn.staticfile.org/typed.js/2.0.11/typed.min.js" ></script>
    <script type="text/javascript">
        !function(e){var n,t={},o="jinrishici-token";function i(){return document.getElementById("jinrishici-sentence")||0!=document.getElementsByClassName("jinrishici-sentence").length}function c(){t.load(function(e){var n=document.getElementById("jinrishici-sentence"),t=document.getElementsByClassName("jinrishici-sentence");if(n&&(n.innerText=e.data.content),0!==t.length)for(var o=0;o<t.length;o++)t[o].innerText=e.data.content})}function r(e,n){var t=new XMLHttpRequest;t.open("get",n),t.withCredentials=!0,t.send(),t.onreadystatechange=function(n){if(4===t.readyState){var o=JSON.parse(t.responseText);"success"===o.status?e(o):console.error("今日诗词API加载失败，错误原因："+o.errMessage)}}}t.load=function(n){return e.localStorage&&e.localStorage.getItem(o)?function(e,n){return r(e,"https://v2.jinrishici.com/one.json?client=browser-sdk/1.2&X-User-Token="+encodeURIComponent(n))}(n,e.localStorage.getItem(o)):function(n){return r(function(t){e.localStorage.setItem(o,t.token),n(t)},"https://v2.jinrishici.com/one.json?client=browser-sdk/1.2")}(n)},e.jinrishici=t,i()?c():(n=function(){i()&&c()},"loading"!=document.readyState?n():document.addEventListener?document.addEventListener("DOMContentLoaded",n):document.attachEvent("onreadystatechange",function(){"complete"==document.readyState&&n()}))}(window); 
        // 输出指定数量空格+引用
        function writeSpace(len){
          len = (len > 10) ? (len > 31 ? 0 : 8) : len
          var space = ''
          for(var i = 0; i<len; i++){
            space += '&emsp;'
          }
          return space+"——";
        }

        // 执行打字机动效
        function startTyping(strs){
          var typed = new Typed('#subtitle', {
          strings: ['  ', strs],
          cursorChar: "_",
          typeSpeed: 50,
          loop: false,
          });
          typed.stop();
          $(document).ready(function () {
            $(".typed-cursor").addClass("h2");
            typed.start();
          }); 
        }
    </script>
  
    <script>
      // 非首页 则输出文章标题
      var strs = document.getElementById('subtitle').title + "&nbsp;";
      startTyping(strs);
    </script>
  


  <script  src="/js/local-search.js" ></script>
  <script>
    (function () {
      var path = "/local-search.xml";
      var inputArea = document.querySelector("#local-search-input");
      inputArea.onclick = function () {
        searchFunc(path, 'local-search-input', 'local-search-result');
        this.onclick = null
      }
    })()
  </script>















<!-- 主题的启动项 保持在最底部 -->
<script  src="/js/boot.js" ></script>



</body>
</html>
